#include "FileMapping.hpp"

#ifdef _WIN32
//初始化函数
//file: 文件名
//xmode: 选择读/写模式
void FileMapping::init(const char* file, int xmode) {
    //记录文件名
    filename = file;

    if (xmode == 0) {
        //以写模式打开文件描述符
        //若文件存在，则将其清空；若不存在则新建文件
        file_descriptor = CreateFile(
            filename.c_str(),
            GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            NULL);
        if (file_descriptor == INVALID_HANDLE_VALUE) {
            cout << "failed to open " << file << " in CreateFile\n";
            exit(1);
        }
        //设置初始文件大小wfilesize
        SetFilePointer(file_descriptor, wfilesize, 0, FILE_BEGIN);
        SetEndOfFile(file_descriptor);
        //更新读/写mode
        mode = xmode;
    }
    else if (xmode == 1) {
        //以读模式打开文件描述符
        //仅可打开已存在文件
        file_descriptor = CreateFile(
            filename.c_str(),
            GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);
        if (file_descriptor == INVALID_HANDLE_VALUE) {
            cout << "failed to open " << file << " in CreateFile\n";
            exit(1);
        }
        //获取文件长度
        rfilesize = GetFileSize(file_descriptor, NULL);
        //映射文件
        mapping_file(rfilesize);
        mode = xmode;
    }
    else {
        cout << "FileMapping has wrong mode!\n";
        exit(1);
    }
}

//返回读文件长度
DWORD FileMapping::len_of_reading_file() { return rfilesize; }

//返回读文件指针
char* FileMapping::read_content() {
    //对象需先初始化
    if (mode == -1) {
        cout << "FileMapping should be inited first\n";
        return nullptr;
    }
    return map_address;
}

//关闭文件映射
void FileMapping::close() {
    //对象需先初始化
    if (mode == -1)
        return;
    //若文件已映射，则关闭映射
    if (is_mapping()) {
        close_mapping();
    }
    //若为写模式，则更新文件大小
    if (mode == 0) {
        SetFilePointer(file_descriptor, cur, 0, FILE_BEGIN);
        SetEndOfFile(file_descriptor);
    }
    //关闭文件描述符
    CloseHandle(file_descriptor);
}
//当前文件是否映射
bool FileMapping::is_mapping() { return map_address != nullptr; }

//映射文件
//fisze: 映射长度
void FileMapping::mapping_file(DWORD fsize) {
    //创建文件映射对象
    file_mappingobj = CreateFileMapping(file_descriptor, NULL, PAGE_READWRITE, 0, 0, NULL);
    //异常则退出
    if (file_mappingobj == NULL) {
        cout << "CreateFileMapping failed!\n";
        exit(1);
    }
    //获取文件映射后的内容指针
    map_address = (char*)MapViewOfFile(file_mappingobj, FILE_MAP_ALL_ACCESS, 0, 0, fsize);
    if (map_address == nullptr) {
        cout << "MapViewOfFile failed!\n";
        exit(1);
    }
}

//关闭文件映射
void FileMapping::close_mapping() {
    //关闭文件映射对象
    CloseHandle(file_mappingobj);
    UnmapViewOfFile(map_address);
    map_address = nullptr;
}

//检查写入内容长度
void FileMapping::write_check(int size) {
    //若写入大小为size的数据后，超过文件大小，则对文件大小扩容
    if (cur + size >= wfilesize) {
        //首先关闭映射
        if (is_mapping()) {
            close_mapping();
        }
        //扩大wfilesize
        while (cur + size >= wfilesize)
            wfilesize *= 1.5;
        //重新设置文件大小
        SetFilePointer(file_descriptor, wfilesize, 0, FILE_BEGIN);
        SetEndOfFile(file_descriptor);
    }
    //重新映射文件
    if (!is_mapping())
        mapping_file(wfilesize);
}
#endif

#ifdef __unix_linux__

//初始化函数
//file: 文件名
//xmode: 选择读/写模式
void FileMapping::init(const char* file, int xmode){
    filename=file;
    mode=xmode;
    if(xmode==0){
        filesize=256;
        fd=open(file,O_CREAT|O_RDWR);
        if(fd<0){
            printf("Failed to open %s\nfd = %d\n",file,fd);
        }

        ftruncate(fd, filesize);
            
        fstat(fd,&file_stat);
        mapping_file(filesize);
    } else if(xmode==1){
        fd=open(file,O_RDONLY);
        if(fd<0){
            printf("Failed to open %s\nfd = %d\n",file,fd);
        }
        fstat(fd,&file_stat);
        filesize=file_stat.st_size;
        cout<<file_stat.st_size<<endl;
        mapping_file(file_stat.st_size);
    }
}

//返回读文件长度
int FileMapping::len_of_reading_file(){
    return filesize;
}

//返回读文件指针
char* FileMapping::read_content(){
    return map_address;
}

void FileMapping::close(){
    //对象需先初始化
    if (mode == -1)
        return;

    if(is_mapping()){
        close_mapping(cur);
    }

    if(mode==0){
        ftruncate(fd, cur);
    }
        
    ::close(fd);
}

bool FileMapping::is_mapping(){
    return map_address !=nullptr;
}

//映射文件
//fisze: 映射长度
void FileMapping::mapping_file(int fsize){
    if(mode==0)
        map_address=(char *)mmap(NULL,fsize*sizeof(char),PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    else if(mode==1)
        map_address=(char *)mmap(NULL,fsize*sizeof(char),PROT_READ, MAP_SHARED, fd, 0);

    if((void *)-1==map_address){
        printf("mmap failed\n");
        exit(1);
    }
}

//关闭文件映射
void FileMapping::close_mapping(int fsize){
    int stat=munmap(map_address,fsize*sizeof(char));
    if(stat==-1){
        printf("munmap failed\n");
    }
    map_address = nullptr;
}

//检查写入内容长度
void FileMapping::write_check(int size){
    if (cur + size >= filesize) {
        //首先关闭映射
        if (is_mapping()) {
            close_mapping(filesize);
        }
        //扩大wfilesize
        while (cur + size >= filesize)
            filesize *= 1.5;
        //重新设置文件大小
        ftruncate(fd, filesize);

    }
    //重新映射文件
    if (!is_mapping())
        mapping_file(filesize);
}
#endif